/**
 * Created by yeanzhi on 17/3/28.
 */
'use strict';
/**
 * Custom module for quilljs to allow user to resize <img> elements
 * (Works on Chrome, Edge, Safari and replaces Firefox's native resize behavior)
 * @see https://quilljs.com/blog/building-a-custom-module/
 */
export class ImageResize {

    constructor(quill, options = {}) {
        console.log(options)
        // save the quill reference and options
        this.quill = quill;
        this.options = options;
        // bind handlers to this instance
        this.handleClick = this.handleClick.bind(this);
        this.handleMousedown = this.handleMousedown.bind(this);
        this.handleMouseup = this.handleMouseup.bind(this);
        this.handleDrag = this.handleDrag.bind(this);
        this.checkImage = this.checkImage.bind(this);
        // track resize handles
        this.boxes = [];
        // disable native image resizing on firefox
        document.execCommand('enableObjectResizing', false, 'false');
        // respond to clicks inside the editor
        this.quill.root.addEventListener('click', this.handleClick, false);
    }

    handleClick(evt) {
        if (evt.target && evt.target.tagName && evt.target.tagName.toUpperCase() == 'IMG') {
            if (this.img === evt.target) {
                // we are already focused on this image
                return;
            }
            if (this.img) {
                // we were just focused on another image
                this.hide();
            }
            // clicked on an image inside the editor
            this.show(evt.target);
        } else if (this.img) {
            // clicked on a non image
            this.hide();
        }
    }

    show(img) {
        // keep track of this img element
        this.img = img;
        this.showResizers();
        this.showSizeDisplay();
        // position the resize handles at the corners
        const rect = this.img.getBoundingClientRect();
        this.positionBoxes(rect);
        this.positionSizeDisplay(rect);
    }

    hide() {
        this.hideResizers();
        this.hideSizeDisplay();
        this.img = undefined;
    }

    showResizers() {
        // prevent spurious text selection
        this.setUserSelect('none');
        // add 4 resize handles
        this.addBox('nwse-resize'); // top left
        this.addBox('nesw-resize'); // top right
        this.addBox('nwse-resize'); // bottom right
        this.addBox('nesw-resize'); // bottom left
        // listen for the image being deleted or moved
        document.addEventListener('keyup', this.checkImage, true);
        this.quill.root.addEventListener('input', this.checkImage, true);
    }

    hideResizers() {
        // stop listening for image deletion or movement
        document.removeEventListener('keyup', this.checkImage);
        this.quill.root.removeEventListener('input', this.checkImage);
        // reset user-select
        this.setUserSelect('');
        this.setCursor('');
        // remove boxes
        this.boxes.forEach(box => document.body.removeChild(box));
        // release memory
        this.dragBox = undefined;
        this.dragStartX = undefined;
        this.preDragWidth = undefined;
        this.boxes = [];
    }

    addBox(cursor) {
        // create div element for resize handle
        const box = document.createElement('div');
        // apply styles
        const styles = {
            position: 'absolute',
            height: '12px',
            width: '12px',
            backgroundColor: 'white',
            border: '1px solid #777',
            boxSizing: 'border-box',
            opacity: '0.80',
            cursor: cursor
        };
        this.extend(box.style, styles, this.options.handleStyles || {});
        // listen for mousedown on each box
        box.addEventListener('mousedown', this.handleMousedown, false);
        // add drag handle to document
        document.body.appendChild(box);
        // keep track of drag handle
        this.boxes.push(box);
    }

    extend(destination, ...sources) {
        sources.forEach(source => {
            for (let prop in source) {
                if (source.hasOwnProperty(prop)) {
                    destination[prop] = source[prop];
                }
            }
        });
        return destination;
    }

    positionBoxes(rect) {
        // set the top and left for each drag handle
        [
            {left: rect.left - 6, top: rect.top - 6},               // top left
            {left: rect.left + rect.width - 6, top: rect.top - 6},               // top right
            {left: rect.left + rect.width - 6, top: rect.top + rect.height - 6}, // bottom right
            {left: rect.left - 6, top: rect.top + rect.height - 6} // bottom left
        ].forEach((pos, idx) => {
            this.extend(this.boxes[idx].style, {
                top: Math.round(pos.top + window.pageYOffset) + 'px',
                left: Math.round(pos.left + window.pageXOffset) + 'px'
            });
        });
    }

    handleMousedown(evt) {
        // note which box
        this.dragBox = evt.target;
        // note starting mousedown position
        this.dragStartX = evt.clientX;
        // store the width before the drag
        this.preDragWidth = this.img.width || this.img.naturalWidth;
        // set the proper cursor everywhere
        this.setCursor(this.dragBox.style.cursor);
        // listen for movement and mouseup
        document.addEventListener('mousemove', this.handleDrag, false);
        document.addEventListener('mouseup', this.handleMouseup, false);
    }

    handleMouseup() {
        // reset cursor everywhere
        this.setCursor('');
        // stop listening for movement and mouseup
        document.removeEventListener('mousemove', this.handleDrag);
        document.removeEventListener('mouseup', this.handleMouseup);
    }

    handleDrag(evt) {
        if (!this.img) {
            // image not set yet
            return;
        }
        // update image size
        if (this.dragBox == this.boxes[0] || this.dragBox == this.boxes[3]) {
            // left-side resize handler; draging right shrinks image
            this.img.width = Math.round(this.preDragWidth - evt.clientX - this.dragStartX);
        } else {
            // right-side resize handler; draging right enlarges image
            this.img.width = Math.round(this.preDragWidth + evt.clientX - this.dragStartX);
        }
        // reposition the drag handles around the image
        const rect = this.img.getBoundingClientRect();
        this.positionBoxes(rect);
        this.positionSizeDisplay(rect);
    }

    setUserSelect(value) {
        [
            'userSelect',
            'mozUserSelect',
            'webkitUserSelect',
            'msUserSelect'
        ].forEach(prop => {
            // set on contenteditable element and <html>
            this.quill.root.style[prop] = value;
            document.documentElement.style[prop] = value;
        });
    }

    setCursor(value) {
        [
            document.body,
            this.img,
            this.quill.root
        ].forEach(el => el.style.cursor = value);
    }

    checkImage() {
        if (this.img) {
            this.hide();
        }
    }

    showSizeDisplay() {
        if (!this.options.displaySize) {
            return;
        }
        this.display = document.createElement('div');
        // apply styles
        const styles = {
            position: 'absolute',
            font: '12px/1.0 Arial, Helvetica, sans-serif',
            padding: '4px 8px',
            textAlign: 'center',
            backgroundColor: 'white',
            color: '#333',
            border: '1px solid #777',
            boxSizing: 'border-box',
            opacity: '0.80',
            cursor: 'default'
        };
        this.extend(this.display.style, styles, this.options.displayStyles || {});
        document.body.appendChild(this.display);
    }

    hideSizeDisplay() {
        if (this.display) {
            document.body.removeChild(this.display);
        }
        this.display = undefined;
    }

    positionSizeDisplay(rect) {
        if (!this.display || !this.img) {
            return;
        }
        const size = this.getCurrentSize();
        this.display.innerHTML = size.join(' &times; ');
        if (size[0] > 120 && size[1] > 30) {
            // position on top of image
            const dispRect = this.display.getBoundingClientRect();
            this.extend(this.display.style, {
                left: Math.round(rect.left + rect.width + window.pageXOffset - dispRect.width - 8) + 'px',
                top: Math.round(rect.top + rect.height + window.pageYOffset - dispRect.height - 8) + 'px'
            });
        } else {
            // position off bottom right
            this.extend(this.display.style, {
                left: Math.round(rect.left + rect.width + window.pageXOffset + 8) + 'px',
                top: Math.round(rect.top + rect.height + window.pageYOffset + 8) + 'px'
            });
        }
    }

    getCurrentSize() {
        return [
            this.img.width,
            Math.round(this.img.width / this.img.naturalWidth * this.img.naturalHeight)
        ];
    }

}
